<template>
  <canvas :ref="id" @click="onClick"></canvas>
</template>

<script>
export default {
  name: 'HeadCropper',
  props: {
    backBoxSize: {
      type: Number,
      default: 10
    },
    backBoxColor0: {
      type: String,
      default: '#fcf2ff'
    },
    backBoxColor1: {
      type: String,
      default: '#a4a4a4'
    },
    outWidth: {
      type: Number,
      default: 256
    },
    outHeight: {
      type: Number,
      default: 256
    },
    maskColor: {
      type: String,
      default: '#00000080'
    },
    maskPadding: {
      type: Number,
      default: 20
    },
    lineWidth: {
      type: Number,
      default: 2
    },
    lineColor: {
      type: String,
      default: '#ffffff'
    },
    isArc: {
      type: Boolean,
      default: true
    },
    round: {
      type: Number,
      default: 10
    },
    src: {
      type: String,
      default: null
    },
    limitations: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      id: 'canvas' + new Date().getTime(),
      width: 1,
      height: 1,
      image: null,
      back: null,
      mask: null,
      left: 0,
      right: 0,
      top: 0,
      bottom: 0,
      centerX: 0,
      centerY: 0,
      scale: 0,
      rotate: 0,
      touchs: [],
      drawX: 0,
      drawY: 0,
      mousePoint: null,
      mouseType: null
    }
  },
  watch: {
    src(to, form) {
      const img = new Image()

      img.onload = () => {
        this.drawX = 0
        this.drawY = 0
        this.rotate = 0
        this.scale = 0
        this.image = img
        this.onDraw()
      }
      img.src = to
    }
  },
  mounted() {
    this.$nextTick(() => {
      const canvas = this.$refs[this.id]

      canvas.addEventListener('touchstart', this.onTouchStart, false)
      canvas.addEventListener('touchmove', this.onTouchMove, false)
      canvas.addEventListener('touchend', this.onTouchEnd, false)

      canvas.onmousewheel = this.onMouseWheel
      canvas.onmousedown = this.onMouseDown
      canvas.onmousemove = this.onMouseMove
      canvas.onmouseup = this.onMouseUp
      canvas.oncontextmenu = () => false

      this.width = canvas.clientWidth
      this.height = canvas.clientHeight
      canvas.width = this.width
      canvas.height = this.height
      this.onPadding()
      this.onDraw()

      if (this.src) {
        const img = new Image()
        img.onload = () => {
          this.image = img
          this.onDraw()
        }
        img.src = this.src
      }
    })
  },
  beforeDestroy() {
    const canvas = this.$refs[this.id]

    canvas.removeEventListener('touchstart', this.onTouchStart)
    canvas.removeEventListener('touchmove', this.onTouchMove)
    canvas.removeEventListener('touchend', this.onTouchEnd)
  },
  methods: {
    onClick(e) {

    },
    onTouchStart(e) {
      this.touchs = e.touches
    },
    getToucheByIdentifier(touchs, identifier) {
      for (const i in touchs) {
        if (touchs[i].identifier === identifier) {
          return touchs[i]
        }
      }
      return null
    },
    onTouchMove(e) {
      if (this.touchs.length === 0) {
        return
      } else if (this.touchs.length > 1 || this.touchs.length > 1) {
        const news0 = e.touches[0]
        const old0 = this.getToucheByIdentifier(this.touchs, news0.identifier)

        const news1 = e.touches[1]
        const old1 = this.getToucheByIdentifier(this.touchs, news1.identifier)
        if (old0 && old1) {
          const newLine = Math.sqrt(Math.pow(news0.clientX - news1.clientX, 2) + Math.pow(news0.clientY - news1.clientY, 2))
          const oldLine = Math.sqrt(Math.pow(old0.clientX - old1.clientX, 2) + Math.pow(old0.clientY - old1.clientY, 2))
          this.scale *= (newLine / oldLine)

          this.drawX += ((news0.clientX - old0.clientX) + (news1.clientX - old1.clientX)) / 2
          this.drawY += ((news0.clientY - old0.clientY) + (news1.clientY - old1.clientY)) / 2

          if (!this.limitations) {
            const k1 = (old0.clientX - old1.clientX) / (old0.clientY - old1.clientY)
            const k2 = (news0.clientX - news1.clientX) / (news0.clientY - news1.clientY)
            this.rotate -= ((k2 - k1) / (1 + k1 * k2) * 90)
          }

          this.onDraw()
          this.touchs = e.touches
          e.preventDefault()
          return
        }
      }

      if (this.touchs.length > 0 || this.touchs.length > 0) {
        var news = e.touches[0]
        var old = this.touchs[0]

        this.drawX += news.clientX - old.clientX
        this.drawY += news.clientY - old.clientY
      }

      this.onDraw()
      this.touchs = e.touches
      e.preventDefault()
    },
    onTouchEnd(e) {
      this.touchs = e.touches
    },
    onMouseWheel(e) {
      if ((e.wheelDelta | e.detail) > 0) {
        this.scale += 0.1
      }
      if ((e.wheelDelta | e.detail) < 0) {
        this.scale -= 0.1
      }
      this.onDraw()
    },
    onMouseDown(e) {
      this.mouseType = e.button
      this.mousePoint = {
        clientX: e.clientX,
        clientY: e.clientY
      }
    },
    onMouseMove(e) {
      if (this.mousePoint == null) {
        return
      }
      if (this.mouseType === 0) {
        this.drawX += e.clientX - this.mousePoint.clientX
        this.drawY += e.clientY - this.mousePoint.clientY
      } else if (this.mouseType === 2 && !this.limitations) {
        this.rotate += e.clientY - this.mousePoint.clientY
      } else {
        e.preventDefault()
        return
      }

      this.onDraw()
      this.mousePoint = {
        clientX: e.clientX,
        clientY: e.clientY
      }
    },
    onMouseUp(e) {
      this.mousePoint = null
      this.mouseType = null
    },
    createBack() {
      const temp = document.createElement('canvas')
      temp.width = this.width
      temp.height = this.height

      const cxt = temp.getContext('2d')
      for (let y = 0; y < temp.height; y += this.backBoxSize) {
        let color = ((y / this.backBoxSize) % 2) ? this.backBoxColor0 : this.backBoxColor1
        for (let x = 0; x < temp.width; x += this.backBoxSize) {
          cxt.fillStyle = (color = color === this.backBoxColor1 ? this.backBoxColor0 : this.backBoxColor1)
          cxt.fillRect(x, y, x + this.backBoxSize, y + this.backBoxSize)
        }
      }

      this.back = new Image()
      this.back.src = temp.toDataURL('image/png')
    },
    craeteMask() {
      var temp = document.createElement('canvas')
      temp.width = this.width
      temp.height = this.height

      var cxt = temp.getContext('2d')

      cxt.fillStyle = this.maskColor
      cxt.beginPath()
      cxt.rect(0, 0, this.width, this.height)
      if (this.isArc) {
        cxt.arc(this.centerX, this.centerY, (this.bottom - this.top) / 2, 0, Math.PI * 2, true)
      } else {
        this.drawRoundRectPath(cxt, this.left, this.top, this.right - this.left, this.bottom - this.top, this.round)
      }
      cxt.closePath()
      cxt.fill()

      cxt.beginPath()
      cxt.strokeStyle = this.lineColor
      cxt.lineWidth = this.lineWidth
      if (this.isArc) {
        cxt.arc(this.centerX, this.centerY, (this.bottom - this.top) / 2, 0, Math.PI * 2, true)
      } else {
        this.drawRoundRectPath(cxt, this.left, this.top, this.right - this.left, this.bottom - this.top, this.round)
      }
      cxt.closePath()
      cxt.stroke()

      this.mask = new Image()
      this.mask.src = temp.toDataURL('image/png')
    },
    onPadding() {
      let fw = this.width / this.outWidth
      const fh = this.height / this.outHeight
      if (fw > fh) {
        fw = fh
      }
      const width = this.outWidth * fw / 2 - this.maskPadding
      const height = this.outHeight * fw / 2 - this.maskPadding
      this.centerX = this.width / 2
      this.centerY = this.height / 2
      this.left = this.centerX - width
      this.right = this.centerX + width
      this.top = this.centerY - height
      this.bottom = this.centerY + height
    },
    drawRoundRectPath(cxt, x, y, width, height, radius) {
      cxt.save()
      cxt.translate(x, y)
      cxt.moveTo(radius, 0)
      cxt.arc(radius, radius, radius, Math.PI * 3 / 2, Math.PI, true)// 左上角圆弧，弧度从PI到3/2PI
      cxt.lineTo(0, radius)// 矩形左边线
      cxt.arc(radius, height - radius, radius, Math.PI, Math.PI / 2, true) // 左下角圆弧，弧度从1/2PI到PI
      cxt.lineTo(radius, height)// 矩形下边线
      cxt.arc(width - radius, height - radius, radius, Math.PI / 2, 0, true)// 从右下角顺时针绘制，弧度从0到1/2PI
      cxt.lineTo(width, height - radius)// 右边线
      cxt.arc(width - radius, radius, radius, Math.PI * 2, Math.PI * 3 / 2, true) // 右上角圆弧
      cxt.lineTo(width - radius, 0)// 上边线
      cxt.restore()
    },
    onPosition() {
      if (this.limitations) {
        if (this.scale > 5) {
          this.scale = 5
        }

        let fw = (this.right - this.left) / this.image.width
        const fh = (this.bottom - this.top) / this.image.height
        if (fw < fh) {
          fw = fh
        }
        if (this.scale < fw) {
          this.scale = fw
        }
        const width = this.image.width * this.scale / 2
        if (this.left < this.centerX + this.drawX - width) {
          this.drawX = this.left - this.centerX + width
        }
        if (this.right > this.centerX + this.drawX + width) {
          this.drawX = this.right - this.centerX - width
        }

        const height = this.image.height * this.scale / 2
        if (this.top < this.centerY + this.drawY - height) {
          this.drawY = this.top - this.centerY + height
        }
        if (this.bottom > this.centerY + this.drawY + height) {
          this.drawY = this.bottom - this.centerY - height
        }
      }
    },
    onDraw() {
      const canvas = this.$refs[this.id]
      const cxt = canvas.getContext('2d')
      if (this.back == null) {
        this.createBack()
      }
      if (this.mask == null) {
        this.craeteMask()
      }

      cxt.drawImage(this.back, 0, 0, this.width, this.height)
      if (this.image) {
        this.onPosition()
        cxt.save()
        cxt.translate(this.centerX + this.drawX, this.centerY + this.drawY)
        cxt.rotate(this.rotate * Math.PI / 180)
        cxt.scale(this.scale, this.scale)
        cxt.drawImage(this.image, -this.image.width / 2, -this.image.height / 2, this.image.width, this.image.height)
        cxt.restore()
      }
      cxt.drawImage(this.mask, 0, 0, this.width, this.height)
    },
    outCanvas() {
      const temp = document.createElement('canvas')
      temp.width = this.outWidth
      temp.height = this.outHeight
      if (this.image) {
        const cxt = temp.getContext('2d')
        const scale = this.outHeight / (this.bottom - this.top)
        cxt.translate(this.outWidth / 2 + this.drawX * scale, this.outHeight / 2 + this.drawY * scale)
        cxt.rotate(this.rotate * Math.PI / 180)
        cxt.scale(this.scale * scale, this.scale * scale)
        cxt.drawImage(this.image, -this.image.width / 2, -this.image.height / 2, this.image.width, this.image.height)
      }
      return temp
    },
    toDataURL(type, quality) {
      const canvas = this.outCanvas()
      return canvas.toDataURL(type, quality)
    },
    toBlob(callback, type, quality) {
      if (callback instanceof Function) {
        const canvas = this.outCanvas()
        canvas.toBlob(callback, type, quality)
      }
    }, getRotate() {
      return this.rotate
    },
    getScale() {
      return this.scale
    },
    getDrawX() {
      return this.drawX
    },
    getDrawY() {
      return this.drawY
    },
    setRotate(val) {
      this.rotate = val
    },
    setScale(val) {
      this.scale = val
    },
    setDrawX(val) {
      this.drawX = val
    },
    setDrawY(val) {
      this.drawY = val
    }
  }
}
</script>

<style scoped>

</style>
